
#include <BFont>
#include <BPainter>
#include "member_BTextEdit.h"

using namespace BWE;

member_BTextEdit::member_BTextEdit(BTextEdit* textEdit)
{
	boss = textEdit;
	lineHeight = 20;
	wordWrap = true;
	lineWrap = true;
	readOnly = false;
	blinkTick = 0.0;
	blinkTime = 1.0;
	inputShown = true;
	readOnly = false;

	dirty = false;
	edited = false;
	changed = false;
	pressed = false;
	gotoInput = false;
}
member_BTextEdit::~member_BTextEdit()
{

}

void member_BTextEdit::initBlocks()
{
	BFont* font = (BFont*)boss->font();
	blocklst.clear();
	if (text.empty())
	{
		blocklst.append(ILineBlock());
		ILineBlock& block = blocklst.last();
		block.reset(text);
		block.setFont(font);
	}
	else
	{
		int pos = 0;
		BString line;
		while (pos < text.size())
		{
			blocklst.append(ILineBlock());
			ILineBlock& block = blocklst.last();
			pos = text.line(line, pos);
			line.remove('\r');
			block.reset(line);
			block.setFont(font);
		}
	}
	changed = false;
	inputBlock = blocklst.end();
	hoverBlock = blocklst.end();
	beginBlock = blocklst.end();
	endBlock = blocklst.end();
}
void member_BTextEdit::appendBlocks(const BString& str)
{
	BFont* font = (BFont*)boss->font();
	int pos = text.size();
	text.append(str);

	BString line;
	if (blocklst.size() && blocklst.last().cells.last() != '\n')
	{
		ILineBlock& block = blocklst.last();
		pos = text.line(line, pos);
		line.remove('\r');
		block.reset(line);
		block.setFont(font);
	}

	while (pos < text.size())
	{
		blocklst.append(ILineBlock());
		ILineBlock& block = blocklst.last();
		pos = text.line(line, pos);
		line.remove('\r');
		block.reset(line);
		block.setFont(font);
	}

	inputBlock = blocklst.end();
	hoverBlock = blocklst.end();
}
void member_BTextEdit::freshBlocks()
{
	const BFont* font = boss->font();
	if (!font)
		return;

	const BRect& crect = boss->centerRect();
	BRect area = boss->area();
	int x = area.x();
	int y = area.y();
	int width = 0;
	int height = crect.height() / 2;
	int pos = 0;
	for (auto it = blocklst.begin(); it.valid(); it++)
	{
		ILineBlock& block = *it;
		if (block.dirtyCells || block.dirtyFont)
		{
			if (lineWrap)
				block.adjust(crect.width());
			else
				block.adjust(0);
		}
		if (block.visible)
		{
			block.rect.setPos(x, y);
			y += block.rect.height();
			height += block.rect.height();
			if (width < block.rect.width())
				width = block.rect.width();
			if (block.rect.width() < crect.width())
				block.rect.setWidth(crect.width());
		}
		block.range.min() = pos;
		block.range.max() = pos + block.cells.size();
		pos = block.range.max();
	}

	area.setWidth(width);
	area.setHeight(height);
	boss->setArea(area);

	freshVisualRange();
	freshInputLine();
	if (gotoInput)
	{
		BRect rect(inputLine[0]);
		rect.expand(inputLine[1]);
		boss->scrollToRect(rect);
		gotoInput = false;
	}
}
void member_BTextEdit::freshVisualRange()
{
	BRect crect = boss->centerRect();
	showBegin = blocklst.begin();
	showEnd = blocklst.end();
	while (showBegin.valid())
	{
		ILineBlock& block = *showBegin;
		if (block.rect.bottom() > crect.top())
		{
			showEnd = showBegin;
			while (showEnd.valid())
			{
				if (showEnd->rect.top() > crect.bottom())
					break;
				showEnd++;
			}
			break;
		}
		showBegin++;
	}
}
void member_BTextEdit::freshInputLine()
{
	if (inputBlock.empty())
		return;
	if (inputBlock->cells.empty())
	{
		BRect rect = inputBlock->rect;
		inputLine[0] = rect.leftTop();
		inputLine[1] = rect.leftBottom();
	}
	else if (inputBlock->inputIndex < inputBlock->cells.size())
	{
		CodeCell& cell = inputBlock->cells[inputBlock->inputIndex];
		BRect rect = cell.rect + inputBlock->rect.pos();
		inputLine[0] = rect.leftTop();
		inputLine[1] = rect.leftBottom();
	}
	else
	{
		CodeCell& cell = inputBlock->cells[inputBlock->inputIndex - 1];
		BRect rect = cell.rect + inputBlock->rect.pos();
		inputLine[0] = rect.rightTop();
		inputLine[1] = rect.rightBottom();
	}
}
void member_BTextEdit::wrapInputLine()
{
	freshInputLine();
	const BRect& crect = boss->centerRect();
	BRect rect(inputLine[0]);
	rect.expand(inputLine[1]);
	boss->scrollToRect(rect);
}

bool member_BTextEdit::seekPrevWord()
{
	if (inputBlock->inputIndex == 0)
	{
		if (inputBlock.index() == 0)
			return false;
		inputBlock--;
		inputBlock->inputIndex = inputBlock->tail();
		inputBlock->inputCount = 0;
	}
	if (inputBlock->seekPrevWord() < 0)
	{
		if (inputBlock.index() == 0)
			return false;
		inputBlock--;
		inputBlock->inputIndex = inputBlock->tail();
		inputBlock->inputCount = 0;
	}
	dirty = true;
	boss->fresh();
	return true;
}
bool member_BTextEdit::seekNextWord()
{
	if (inputBlock->cells(inputBlock->inputIndex).code == '\n')
	{
		if (inputBlock.index() == blocklst.size() - 1)
			return false;
		inputBlock++;
		inputBlock->inputIndex = 0;
		inputBlock->inputCount = 0;
	}
	if (inputBlock->seekNextWord() >= inputBlock->cells.size())
	{
		if (inputBlock.index() == blocklst.size() - 1)
			return false;
		inputBlock++;
		inputBlock->inputIndex = 0;
		inputBlock->inputCount = 0;
	}
	dirty = true;
	boss->fresh();
	return true;
}
bool member_BTextEdit::seekPrev()
{
	if (inputBlock->seekPrevCode() < 0)
	{
		if (inputBlock.index() == 0)
			return false;
		inputBlock--;
		inputBlock->inputIndex = inputBlock->tail();
		inputBlock->inputCount = 0;
	}
	dirty = true;
	boss->fresh();
	return true;
}
bool member_BTextEdit::seekNext()
{
	if (inputBlock->seekNextCode() >= inputBlock->cells.size())
	{
		if (inputBlock.index() == blocklst.size() - 1)
			return false;
		inputBlock++;
		inputBlock->inputIndex = 0;
		inputBlock->inputCount = 0;
	}
	dirty = true;
	boss->fresh();
	return true;
}
bool member_BTextEdit::seekUp()
{
	BPoint point = (inputLine[0] + inputLine[1]) / 2;
	point.y() -= lineHeight / 2;
	int index = inputBlock->indexAt(point);
	if (index >= 0)
	{
		inputBlock->inputIndex = index;
	}
	else
	{
		if (inputBlock.index() <= 0)
			return false;
		inputBlock--;
		inputBlock->inputIndex = inputBlock->indexAt(point);
	}
	inputBlock->inputCount = 0;
	dirty = true;
	boss->fresh();
	return true;
}
bool member_BTextEdit::seekDown()
{
	BPoint point = (inputLine[0] + inputLine[1]) / 2;
	point.y() += lineHeight / 2;
	int index = inputBlock->indexAt(point);
	if (index < inputBlock->cells.size())
	{
		inputBlock->inputIndex = index;
	}
	else
	{
		if (inputBlock.index() == blocklst.size() - 1)
			return false;
		inputBlock++;
		inputBlock->inputIndex = inputBlock->indexAt(point);
	}
	inputBlock->inputCount = 0;
	dirty = true;
	boss->fresh();
	return true;
}

bool member_BTextEdit::input(const BCode& code)
{
	if (code == '\n')
	{
		inputBlock->input(code);

		BString text = inputBlock->substr(inputBlock->inputIndex, inputBlock->cells.size() - inputBlock->inputIndex);
		inputBlock->remove(inputBlock->inputIndex, inputBlock->cells.size() - inputBlock->inputIndex);

		blocklst.insert(inputBlock + 1, ILineBlock());
		ILineBlockIterator next = inputBlock + 1;
		next->reset(text);
		next->setFont(boss->font());

		inputBlock = next;
	}
	else
	{
		inputBlock->input(code);
	}
	dirty = true;
	edited = true;
	changed = true;
	gotoInput = true;
	boss->emit(Signal_TextEdited, inputBlock.index());
	boss->fresh();
	return true;
}
bool member_BTextEdit::input(const BString& text)
{
	int pos = 0;
	BString line;
	while (pos < text.size())
	{
		pos = text.line(line, pos);
		line.remove('\r');
		inputBlock->input(line);
		int split_pos = inputBlock->cells.rfind('\n', inputBlock->tail() - 1);
		if (split_pos >= 0)
		{
			blocklst.insert(inputBlock + 1, ILineBlock());
			inputBlock->inputIndex = split_pos + 1;
			inputBlock->inputCount = inputBlock->cells.size() - inputBlock->inputIndex;
			BString nextstr = inputBlock->selection();
			inputBlock->removeSelection();
		
			inputBlock += 1;
			inputBlock->reset(nextstr);
			inputBlock->setFont(boss->font());
		}
		edited = true;
		changed = true;
	}
	dirty = true;
	gotoInput = true;
	boss->emit(Signal_TextEdited, inputBlock.index());
	boss->fresh();
	return true;
}

bool member_BTextEdit::selectBlocks()
{
	if (pressBlock.empty() || hoverBlock.empty())
		return false;
	if (pressBlock == hoverBlock)
	{
		inputBlock = pressBlock;
		inputBlock->inputCount = abs(pressIndex - hoverIndex);
		inputBlock->inputIndex = bMin(pressIndex, hoverIndex);
	}
	else
	{
		if (pressBlock < hoverBlock)
		{
			beginBlock = pressBlock;
			beginBlock->inputIndex = pressIndex;
			beginBlock->inputCount = beginBlock->cells.size() - pressIndex;
			endBlock = hoverBlock;
			endBlock->inputIndex = 0;
			endBlock->inputCount = hoverIndex;
		}
		else
		{
			beginBlock = hoverBlock;
			beginBlock->inputIndex = hoverIndex;
			beginBlock->inputCount = beginBlock->cells.size() - hoverIndex;
			endBlock = pressBlock;
			endBlock->inputIndex = 0;
			endBlock->inputCount = pressIndex;
		}
		for (auto it = beginBlock + 1; it < endBlock; it++)
		{
			it->inputIndex = 0;
			it->inputCount = it->cells.size();
		}
	}
	return true;
}
void member_BTextEdit::unselected()
{
	if (inputBlock.valid())
		inputBlock->inputCount = 0;
	if (beginBlock.valid())
	{
		beginBlock->inputCount = 0;
		beginBlock.reset();
	}
	if (endBlock.valid())
	{
		endBlock->inputCount = 0;
		endBlock.reset();
	}
}
bool member_BTextEdit::selectedText(BString& text)
{
	if (beginBlock.valid())
	{
		for (auto it = beginBlock; it <= endBlock; it++)
		{
			text.append(it->selection());
		}
		return true;
	}
	else
	{
		text = inputBlock->selection();
		return true;
	}
	return !text.empty();
}

bool member_BTextEdit::removeSelection()
{
	if (beginBlock.valid())
	{
		for (auto it = beginBlock; it <= endBlock; it++)
		{
			it->removeSelection();
		}
		auto nextBlock = beginBlock + 1;
		blocklst.erase(nextBlock, endBlock);
		if (beginBlock->cells.last() != '\n')
		{
			beginBlock->append(endBlock->text);
			blocklst.erase(endBlock);
		}
		inputBlock = beginBlock;
		hoverBlock.reset();
		beginBlock.reset();
		endBlock.reset();
		showBegin.reset();
		showEnd.reset();

		dirty = true;
		edited = true;
		changed = true;
		boss->emit(Signal_TextEdited, inputBlock.index());
		boss->fresh();
		return true;
	}
	if (inputBlock.valid() && inputBlock->inputCount)
	{
		if (inputBlock->removeSelection())
		{
			if (inputBlock->cells.last() != '\n')
			{
				ILineBlockIterator nextBlock = inputBlock + 1;
				if (nextBlock.valid())
				{
					inputBlock->append(nextBlock->text);
					blocklst.erase(nextBlock);
				}
			}
			changed = true;
			edited = true;
			dirty = true;
			boss->emit(Signal_TextEdited, inputBlock.index());
			boss->fresh();
			return true;
		}
	}

	return false;
}
bool member_BTextEdit::removeIndex()
{
	inputBlock->remove(inputBlock->inputIndex);
	if (inputBlock->cells.last() != '\n' && inputBlock.index() < blocklst.size() - 1)
	{
		ILineBlock& nextBlock = blocklst[inputBlock.index() + 1];
		inputBlock->cells.append(nextBlock.cells);
		inputBlock->flushText();
		blocklst.remove(inputBlock.index() + 1);
	}
	changed = true;
	edited = true;
	dirty = true;
	boss->emit(Signal_TextEdited, inputBlock.index());
	boss->fresh();
	return true;
}

